package org.jpush.core;

import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.LinkedList;
import java.util.List;

import org.jpush.core.socket.JPushClientSocket;
import org.jpush.utils.IPRange;

/**
 * This class is to manage the <code>PhyscialConnection</code>
 * It will receive the client socket from the <code>PhyscialConnection</code>
 * and create a <code>PhyscialConnection</code> object for this socket.
 * 
 * @author Lei Wang
 *
 */
public class PhysicalConnectionManager
{
	private Object lock;
	
	// Current Connection List
	private List<PhysicalConnection> pendingPhysicalConnectionList
		= new LinkedList<PhysicalConnection>();

	// Login timeout
    private int uLoginExpireDuration;

    // The maximum connections are allowed
    private int nMaxConnections;

    // The List of block IP list and permitted IP list
    // which is set up
    private List<IPRange> blockedIPs = new LinkedList<IPRange>();
    private List<IPRange> permittedIPs = new LinkedList<IPRange>();

	protected Server server;

    // If true, any IP within the Allow List will be permitted
	// Otherwise, any IP not within the Block List will be permitted
    private boolean isPermitOnly;
    
    private static final int LOGIN_TIMEOUT = 40;
    private static final int MAX_CONNECTIONS = 100;

    public PhysicalConnectionManager(Server serverImpl)
    {
    	this.lock = new Object();

    	this.uLoginExpireDuration = LOGIN_TIMEOUT;
        this.nMaxConnections = MAX_CONNECTIONS;

        this.isPermitOnly = false;
        
        this.server = serverImpl;
    }

    /**
     * Create a PhysicalConnection for the socket and register it into the 
     * list. Also it will send the PhysicalConnection to the Dispatcher
     * @param socket           The client socket
     * @param listenerOptions  The listener Options
     * @param blnIsObserver    true if this is socket for monitor use
     * @return  true if succeed.
     */
    private boolean setupPhysicalConnection(JPushClientSocket socket, 
    		ListenerOptions listenerOptions, boolean blnIsObserver)
    {
        PhysicalConnection connection = new PhysicalConnection(socket, 
        		blnIsObserver, listenerOptions, server);
        
        if (!server.getConnectionEventQueue().register(connection))
        {
        	return false;
        }

//        addToConnectedList(connection);
//        if (!serverImpl.getDispatcher().addSocketContext(socket, connection))
//        {
//            removeFromUnattachedList(connection);
//            return false;
//        }

        return true;
    }

    private boolean isAddressAllowed(JPushClientSocket socket)
    {
    	SocketAddress sockAddr = socket.getRemoteAddress();
    	
        if ((sockAddr != null) && (sockAddr instanceof InetSocketAddress))
        {
        	InetSocketAddress inetSockAddr = (InetSocketAddress)sockAddr;
        	byte[] ip = inetSockAddr.getAddress().getAddress();

            List<IPRange> list = isPermitOnly ? permittedIPs : blockedIPs;
            boolean bIsInList = false;
            for (IPRange ipRange : list)
            {
                if (ipRange.isWithin(ip))
                {
                    bIsInList = true;
                    break;
                }
            }

            return isPermitOnly ? bIsInList : !bIsInList;
        }
        else
        {
        	return false;
        }
    }


    public void setMaxAllowedConnections(int nMaxConnections)
    {
        this.nMaxConnections = nMaxConnections;
    }

    public boolean setupClientConnection(JPushClientSocket socket, AbstractAcceptor acceptor)
    {
        if (server.getClientFactory().getConnectionCount() >= nMaxConnections)
        {
            return false;
        }

        //Check if address is not blocked
        if (!isAddressAllowed(socket))
        {
            return false;
        }

        //
        return setupPhysicalConnection(socket, acceptor.getOptions(), false);
    }

    public boolean setupDashboardConnection(JPushClientSocket socket, AbstractAcceptor acceptor)
    {
        return setupPhysicalConnection(socket, acceptor.getOptions(), true);
    }

    public void setLoginExpiryDuration(int uLoginExpireDuration)
    {
        this.uLoginExpireDuration = uLoginExpireDuration;
    }

    public void addIPRangeAccess(String ipStart, String ipStop, boolean bPermit)
    {
        isPermitOnly = bPermit;
        List<IPRange> list = bPermit ? permittedIPs : blockedIPs;

        list.add(new IPRange(ipStart, ipStop));
    }


    public void addToConnectedList(PhysicalConnection connection)
    {    	
    	synchronized (lock)
    	{
    		pendingPhysicalConnectionList.add(connection);
    	}
    }

    public boolean removeFromUnattachedList(PhysicalConnection connection)
    {    	
    	synchronized (lock)
    	{
    		return pendingPhysicalConnectionList.remove(connection);
    	}
    }

    public void closeNonLogged()
    {
    	synchronized(lock)
    	{
    		int nMaxDuration;
    		for (PhysicalConnection pChannel : pendingPhysicalConnectionList)
    		{
    			nMaxDuration = pChannel.isObserverChannel() ? 40 : uLoginExpireDuration;
				if (pChannel.getLifeDuration() > nMaxDuration)
				{
				    pChannel.close(false);
				}
    		}
    	}
    }
}
